/**
 * @copyright Copyright (c) 2022 OnMicro Corp.
 * @brief     Icarus Verilog's VPI wrapper.
 * @author    olof.kindgren@gmail.com, wei.lu@onmicro.com.cn
 * @license   SPDX-License-Identifier: Apache-2.0
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <vpi_user.h>

static bool is_elf_loaded, print_started, omit_print_inside;
static int call_depth, freeze_depth, printf_depth;

extern bool elf_load_file(char *filename);
extern char *elf_get_symbol(uint32_t address);
extern void elf_close_file(void);

static int hello_calltf(char *user_data)
{
  (void)user_data;
  vpi_printf("[CPU] Hello, World!\n");
  return 0;
}

static void calltf_elf_load_file(void)
{
  vpiHandle func_h, args_iter, arg_h;
  struct t_vpi_value argval;

  char *elf_file_name;

  func_h = vpi_handle(vpiSysTfCall, NULL);
  args_iter = vpi_iterate(vpiArgument, func_h);

  if (!is_elf_loaded) {
    arg_h = vpi_scan(args_iter);
    argval.format = vpiStringVal;
    vpi_get_value(arg_h, &argval);
    elf_file_name = argval.value.str;

    while (isspace(*elf_file_name)) {
      elf_file_name++;
    }

    is_elf_loaded = elf_load_file(elf_file_name);
    if (is_elf_loaded) {
      vpi_printf("[CPU] %s was loaded\n", elf_file_name);
    } else {
      vpi_printf("Error: failed to load elf file from \"%s\"\n", elf_file_name);
    }
  }
}

static void calltf_elf_get_symbol(void)
{
  vpiHandle func_h, args_iter, arg_h, time_iter, time_event;
  struct t_vpi_value argval;
  struct t_vpi_time time;

  unsigned int address;
  int ii;
  char *name;

  if (!is_elf_loaded) {
    vpi_printf("Error: $elf_load_file must be called firstly\n");
    return;
  }

  func_h = vpi_handle(vpiSysTfCall, NULL);
  args_iter = vpi_iterate(vpiArgument, func_h);

  arg_h = vpi_scan(args_iter);
  argval.format = vpiIntVal;
  vpi_get_value(arg_h, &argval);
  address = argval.value.integer;

#if 0//assert
  time_iter = vpi_iterate(vpiTimeVar, NULL);
  time_event = vpi_scan(time_iter);
  time.type = vpiScaledRealTime;
  vpi_get_time(time_event, &time);
  vpi_printf("[%lf]\n", time.real);
#endif

  name = elf_get_symbol(address);
#if 0
  /* Omit fputc() */
  if (0 == memcmp(name, "tty_putc", 8)) {
    print_started = true;
    omit_print_inside = true;
    /* Save current record depth when print is omited. */
    printf_depth = call_depth;
    freeze_depth = call_depth;
  }
#else
  /* Omit internals of cprintf() */
  if (0 == memcmp(name, "cprintf", 7)) {
    print_started = true;
    printf_depth = call_depth;
  }
  if (print_started && (call_depth > printf_depth)) {
    print_started = false;
    omit_print_inside = true;
    /* Save current record depth when print is omited. */
    freeze_depth = call_depth;
  }
#endif
  //vpi_printf("addr=%x depth=%d,printf=%d,freeze=%d omit=%d,inside=%d\n", address, call_depth, printf_depth, freeze_depth, print_started, omit_print_inside);
  if (!omit_print_inside) {
    vpi_printf("[CPU] ");
    if (0 != address) {
      for (ii=0; ii<call_depth; ii++) {
        vpi_printf(" ");
      }
      vpi_printf("enter %s@0x%x\n", name, address);
      call_depth++;
    } else {
      call_depth--;
      for (ii=0; ii<call_depth; ii++) {
        vpi_printf(" ");
      }
      vpi_printf("exit\n");
    }
  } else {
    /* Omit print but keep trace of call depth. */
    if (0 != address) {
      call_depth++;
    } else {
      call_depth--;
      /* Resume print if exit to the saved point. */
      if (printf_depth == call_depth) {
        print_started = false;
        omit_print_inside = false;
        printf_depth = 0;
        freeze_depth = 0;
        
        vpi_printf("[CPU] ");
        for (ii=0; ii<call_depth; ii++) {
          vpi_printf(" ");
        }
        vpi_printf("exit\n");
      }
    }
  }
  vpi_flush();

  argval.format = vpiIntVal;
  argval.value.integer = call_depth;
  vpi_put_value(func_h, &argval, NULL, vpiNoDelay);

  vpi_free_object(args_iter);
}

static void setup_user_functions(void)
{
  s_vpi_systf_data task_data_s;
  p_vpi_systf_data task_data_p = &task_data_s;

  task_data_p->type = vpiSysTask;
  task_data_p->sysfunctype = vpiIntFunc;
  task_data_p->tfname = "$hello";
  task_data_p->calltf = hello_calltf;
  task_data_p->compiletf = 0;
  task_data_p->sizetf = 0;
  task_data_p->user_data = 0;
  vpi_register_systf(task_data_p);

  task_data_p->type = vpiSysFunc;
  task_data_p->tfname = "$elf_load_file";
  task_data_p->calltf = (void *)calltf_elf_load_file;
  task_data_p->compiletf = 0;
  vpi_register_systf(task_data_p);

  task_data_p->type = vpiSysFunc;
  task_data_p->tfname = "$elf_get_symbol";
  task_data_p->calltf = (void *)calltf_elf_get_symbol;
  task_data_p->compiletf = 0;
  vpi_register_systf(task_data_p);
}

static void sim_finish_callback(void)
{
  elf_close_file();
}

static void setup_finish_callbacks(void)
{
  static s_vpi_time time_s = {vpiScaledRealTime, 0, 0, 0.0};
  static s_vpi_value value_s = {vpiBinStrVal, {}};
  static s_cb_data cb_data_s = {
    cbEndOfSimulation,
    (void *)sim_finish_callback,
    NULL,
    &time_s,
    &value_s,
    0,
    0,
  };

  cb_data_s.obj = NULL;
  cb_data_s.user_data = NULL;
  vpi_register_cb(&cb_data_s);
}

void (*vlog_startup_routines[])(void) = {
  //setup_reset_callbacks,
  //setup_endofcompile_callbacks,
  setup_finish_callbacks,
  setup_user_functions,
  0,
};
